x = 100
print(id(globals))
print(id(locals))
globals()
def test():
x = "hello world"
print(locals())
print(id(globals))
print(id(locals))
test()
所以,我们可以直接修改名字空间建立关联引用
globals()["hello"] = "hello world"
hello
并非所有时候都能直接操作名字空间,函数执行使用缓存机制,直接修改本地名字空间未必有效。正常编码时候尽量避免直接修改名字空间
在名字空间字典中,名字只是简单的字符串主键,所以,名字可以重新关联另一个对象,不用在乎类型是否相同
x = 100
print(id(x))
x = "hello" # 重新关联对象,而不是修改原对象
print(id(x))
一个对象可以用多个名字
x = 1234
y = x
y is x # 必须用 is 判断是否引用同一个对象,因为相等操作符是可以重载的,有时候只判断值
命名规则建议:
- 类型名称使用 CapWords 格式
- 模块文件名、函数、方法成员等使用 lower_case_with_underscores 格式
- 全局变量使用 UPPER_CASE_WITH_UNDERSCORES 格式
- 避免与内置函数或标准库的常用类型同名,以免造成误解
以下划线开头的名字,代表特殊含义:
- 模块成员以单下划线开头 `(_x)`,属于私有成员,不会被星号导入
- 类型成员以双下划线开头,但无结尾 `(__x)` 属于自动命名私有成员
- 以双下划线开头和结尾 `(__x__)` 通常是系统成员,应避免使用
- 交互模式下,单下划线 `(_)` 返回最后一个表达式结果
1 + 2 + 3
_
强引用¶
import sys
a = 1234
b = a
print(sys.getrefcount(a)) # getrefcount() 也会通过参数引用目标对象,导致引用计数 +1
del a
print(sys.getrefcount(b))
弱引用¶
弱引用(weak reference)在保留引用前提下,不增加计数也不阻止目标被回收(int tuple 等不支持弱引用)
import weakref
class X:
def __del__(self):
print(id(self), "dead.")
c = X()
sys.getrefcount(c)
w = weakref.ref(c)
w() is c
sys.getrefcount(c)
del c
w() is None
弱引用经常用来缓存,监控等 “外挂” 场景,不影响目标对象,也不能阻止它们被回收,弱引用另一个典型应用是实现 Finalizer,也就是在对象被回收时执行额外的 “清理” 操作
d = X()
def callback(w):
print(w, w() is None)
w = weakref.ref(d, callback) # 创建弱引用时设置回调函数
del d
这里不用析构方法的原因是析构函数作为目标成员,用途是完成对象的内部资源清理,它并应该处理与之无关的外部场景,所有用 Finalizer 是一个合理的选择
弱引用与普通名字最大区别在于类函数的调用和语法。可以用 proxy 改进,使其和名字引用语法保持一致
a = X()
a.name = "kaka"
w = weakref.ref(a)
w.name
w().name
p = weakref.proxy(a)
p
p.name
p.age = 60 # 可以直接赋值
p.age
对象复制¶
浅拷贝复制复制名字引用,深拷贝复制所有引用成员
class X: pass
x = X()
x.data = [1, 2]
import copy
x2 = copy.copy(x)
x2 is x
x2.data is x.data # 成员 data 仍然指向原列表,仅仅复制了引用
x3 = copy.deepcopy(x)
x3 is x
x3.data is x.data
循环引用垃圾回收¶
class X:
def __del__(self):
print(self, "dead.")
import gc
gc.disable() # 在性能测试时,要关闭 gc, 避免垃圾回收对执行器计时造成影响
a = X()
b = X()
a.x = b
b.x = a # 构建循环引用
del a
del b # 删除所有名字后对象并未回收,引用计数失效
gc.enable()
gc.collect()
编译¶
除了交互模式和手工编译,源码在被导入(import)时完成编译,编译后的字节码数据被缓存复用,通常还会保存到硬盘
Python 3 使用专门目录保存字节码缓存文件(__pycache__/*.pyc) 这样程序在下次启动时,可以避免再次编译,提升导入速度。缓存文件头中存储了编译信息,用来判断源码文件是否被更新
除了作为执行指令的字节码外,还有很多元数据,共同组成执行单元。从这些元数据中,可以获得参数,闭包等信息
def add(x, y):
return x + y
add.__code__
dir(add.__code__)
add.__code__.co_varnames
add.__code__.co_code
我们无法直接阅读机器码,可以反编译
import dis
dis.dis(add)
某些时候,需要手工编译
source = """
print("hello, world")
print(1 + 2)
"""
code = compile(source, "demo", "exec") # 提供一个文件名用于输出提示
dis.show_code(code)
dis.dis(code)
import py_compile, compileall
path = '/home/kaka/kaka/udacity/1-LaneLines/tune.py'
py_compile.compile(path)
compileall.compile_dir('.') # python -m compileall .
执行¶
程序可以再运行期间动态执行 “未知” 代码,常用于实现动态生成的设计,例如 namedtuple 可以在运行期间构建新的类型
import collections
User = collections.namedtuple("User", "name, age", verbose=True)
User
u = User("kaka", 30)
u
且不管代码如何生成,最终都要以模块导入执行,要么调用 eval,exec 函数执行。eval 执行单个表达式,exec 对应代码块执行,接受字符串或已编译好的代码对象(code) 作为参数。如果是字符串,就会检查是否符合语法规则
s = "1 + 2 + 3"
eval(s)
s = """
def test():
print("hello world")
test()
"""
exec(s)
无论哪种方式,都必须有对应的上下文环境,默认直接使用 当前全局和本地名字空间
x = 100
def test ():
y = 200
print(eval("x + y")) # 从上下文空间获取 x, y
test()
def test():
print("test:", id(globals), id(locals))
exec('print("exec:", id(globals), id(locals))')
test()
有了操作上下文名字空间能力,我们就可以向外部环境注入新的成员,新的类型算法等等。最终达到动态逻辑或结果融入,成为当前体系组成的设计目标
s = """
class X: pass
def hello():
print("hello, world")
"""
exec(s)
X
X()
hello()
某些时候,动态代码来源不确定,基于安全考虑,必须对执行过程进行隔离,阻止其直接读写环境数据。如此,就必须传入容器对象作为动态代码的专用名字空间,以类似简易沙箱(sandbox)的方式执行
根据需要,分别提供 globals,locals参数,也可共用同一空间字典
为保证代码正确执行,解释器会自动导入 __builtins__ 模块。以便导入内置函数
g = {"x": 100}
l = {"y": 200}
eval("x+y", g, l) # 为 globals 和 locals 分别指定字典
ns = { }
exec("class X: pass", ns) # globals 和 locals 共用一个字典
# ns 太多了,不打印了
同时提供两个名字空间参数时,默认总是 locals 优先,除非在动态代码中明确指定使用 globals
s = """
print(x) # locals
global y # globals
y += 100
z = x + y # locals
"""
g = {"x": 10, "y": 20}
l = {"x": 1000}
exec(s, g, l)
在函数作用域内,locals 函数总是返回执行栈帧(stack frame) 名字空间。因此,即便显示提供 locals 名字看完我,也无法将其注入到动态代码中
s = """
print(id(locals()))
def test():
print(id(locals()))
test()
"""
ns = {}
id(ns)
exec(s, ns, ns) # test.locals() 和 ns.locals() 不同
78_654_321 # python 3.6 才支持
0b110011 # 二进制 0b 开头
0o12 # 八进制以 0o 或者 0O 开头
0x64 # 16 进制
0b_11001_1
转换
bin(100) # 10 进制转 2 进制
oct(100) # 10 进制转 8 进制
hex(100) # 10 进制转 16 进制
int 函数默认为十进制,会忽略空格,制表符等空白符,如果指定进制,可忽略相关进制前缀
int("0b1100100", 2)
int("0o144", 8)
int("0x64", 16)
int("64", 16) # 忽略进制前缀
eval("0o144") # 用 eval 完成进制转换,但是效率差些
将整数转换成字节数组,常用于二进制网络协议和文件读写,需要指定字节序,也就是大小端
import sys
x = 0x1234
n = (x.bit_length() + 8 - 1) // 8 # 按 8 位对齐所需的字节数
b = x.to_bytes(n, sys.byteorder)
b.hex(), type(b)
hex(int.from_bytes(b, sys.byteorder))
运算符¶
3 / 2 # Python 3.6 中两个整数相除结果是 float
4 / 2
3 // 2 # Floor Division 会截掉小数部分
5 % 2 # 取余
divmod(5, 2) # 取余的另一种方法
1 > "" # Python 3 不再支持数字和非数字的类型比较操作|
issubclass(bool, int) # bool 是 整数 的子类型,可以直接当做数字使用
isinstance(True, int)
True == 1
True + 1
在进行 bool 转换时,数字 0,None, 空序列和空字典都被视为 False,反之为 True, 如果是自定义类型,可以通过重写 __bool__ 或者 __len__ 方法影响转换结果
data = (0, 0.0, None, "", list(), tuple(), dict(), set(), frozenset())
any(map(bool, data))
枚举¶
import enum
Color = enum.Enum("Color", "BLACK YELLOW BLUE RED")
isinstance(Color.BLACK, Color)
list(Color)
class X(enum.Enum): #通过继承,枚举可以使任意类型
A = "a" # 枚举名字唯一
B = 100
C = [1, 2, 3]
X.C
print(X.B.name) # 每个枚举值都有 name 和 value
print(X.B.value)
print(X["B"])
print(X([1, 2, 3]))
class X(enum.Enum):
A = 1
B = 1
print(X.A)
X(1) # 返回第一个定义项,要是想避免值相同的枚举定义,可以用 enum.unique
内存¶
对于常用的小数字,解释器会在初始化的时候进行预缓存。稍后使用,直接将名字与这些缓存对象关联即可。可以提高性能,Python 3.6 预缓存范围是 [-5, 256]
a = -5
b = -5
a is b
a = 256
b = 256
a is b
a = -6 # 超过缓存,每次都要新建对象,这包括了内存分配等操作
b = -6
a is b
a = 257
b = 257
a is b
浮点数¶
默认浮点数类型仅存储双精度 (double) 浮点数,可表达 16 到 17 个小数位, 从实现方式看,浮点数以二进制存储十进制数的近似值,这可能导致执行结果与编码预期不符,造成不一致的缺陷。所以,对精度有严格要求的场合,应该选择固定精度
1 / 3
可以通过 float.hex 方法输出实际存储的 16 进制格式字符串,以检查执行结果为何不同,还可以用这种方式实现浮点数值的精确传递,避免精度丢失
0.1 * 3 == 0.3
(0.1 * 3).hex()
(0.3).hex()
s = (1 / 3).hex()
float.fromhex(s) # 反向转成浮点数
round(0.1 * 3, 2) == round(0.3, 2) # 使用 round 固定精度,更精确的做法是用 decimal.Decimal 类型
round(0.1, 2) * 3 == round(0.3, 2) # 用 round 返回值做操作数,又损失了精度
float(100)
float("-100.23")
float("\t 100.123 \n") # 与 int 类似,可以处理空白符
float("1.23e45") # 科学计数法
int(2.6), int(-2.6) # 向 0 截小数
from math import trunc, floor, ceil
trunc(2.6), trunc(-2.6) # 截断小数
floor(2.6), floor(-2.6) # 地板截(向下)
ceil(2.6), ceil(-2.6) # 向上截
与 float 基于硬件的二进制浮点数类型相比, decimal.Decimal 是 十进制 实现,最高可提供 28 位有效精度,能准确的表达 十进制 数和运算,不存在二进制近似问题
1.1 + 2.2
(0.1 + 0.1 + 0.1 - 0.3) == 0
from decimal import Decimal
Decimal("1.1") + Decimal("2.2")
(Decimal("0.1") + Decimal("0.1") + Decimal("0.1") - Decimal("0.3")) == 0
在创建 Decimal 实例时,应该传入一个准确数值,比如整数或字符串等,如果是 float 类型,那么构建之前,精度就已经丢失
Decimal(0.1)
Decimal("0.1")
from decimal import Decimal, getcontext
getcontext()
getcontext().prec = 2 # 修改默认的 28 位精度
Decimal(1) / Decimal(3)
from decimal import localcontext
with localcontext() as ctx: # 在一段范围内修改精度
ctx.prec = 5
print(getcontext().prec)
print(Decimal(1) / Decimal(3))
round(0.5) # 因为近似值和精度问题,造成对 float 进行四舍五入的操作会有些问题
round(1.5)
from decimal import Decimal, ROUND_HALF_UP # 可以用 Decimal 避免这种问题
def roundx(x, n):
return Decimal(x).quantize(Decimal(n), ROUND_HALF_UP) # 严格按照 四舍五入 进行
roundx("1.24", ".1")
roundx("1.25", ".1")
roundx("1.26", ".1")
字符串¶
字符串存储 Unicode 文本,是不可变序列类型。UTF 的作用是将码点整数转成计算机可存储的字节格式。UTF-8 与 ASCII 兼容,最常用
s = "汉字"
len(s)
hex(ord("汉"))
chr(0x6c49)
ascii("汉字")
"h\x69, \u6C49\U00005B57" # 大 U 和 小 u 分别表示 32 位和 16 位 整数
type(u"abc") # 默认 str 就是 Unicode 不用加 前缀
type(b"abc") # 字节数组
import dis
def test():
a = "x" + "y" + "z" # 编译期间就算出了结果
b = "a" * 10
return a, b
dis.dis(test)
多个字符串动态拼接,优先使用 join 和 format
join 函数可以预先计算总长度,一次性分配,随后直接复制内存数据填充。另一方面,将固定模板内容与变量分离的 format 更容易阅读和维护
username = "kaka"
datetime = "2018"
tmp = "/data/{user}/message/{time}.txt"
tmp.format(user=username, time=datetime)
s = "-" * 1024
s1 = s[10:1000]
s2 = s[:]
s3 = s.split(",")[0] # 内容相同
s1 is s
s2 is s
s3 is s
s = "汉字"
b = s.encode("utf-16")
b.decode("utf-16")
处理 BOM 信息可以导入 codecs 模块
s = "汉字"
s.encode("utf-16").hex()
import codecs
codecs.BOM_UTF16_LE.hex() # BOM 标志
codecs.encode(s, "utf-16be").hex() # BOM 转换
codecs.encode(s, "utf-16le").hex()
sys.getdefaultencoding() # Python 3 的默认编码不是 ASCII,无需额外设置
Python 3.6 新增了 f-strings 支持,这在多数脚本语言属于标配,使用 f 前缀,解析器解析大括号内的字段和表达式,在上下文名字空间查找同名对象进行替换,格式化控制仍旧遵循 format 规范,但阅读体验更好
x = 10
y = 20
f"{x} + {y} = {x + y}"
"{} + {} = {}".format(x, y, x+y)
f"{type(x)}" # 除了运算符外,还可以是函数调用
"{0} {1} {0}".format("a", 10) # 手工序号
"{} {}".format("a", 10) # 自动序号
"{x} {y}".format(x = 100, y = [1, 2, 3]) # 主键
class X: name = "admin"
x = X()
x.name = "jack"
"{0.name}".format(x) # 属性
"{0[2]}".format([1, 2, 3, 4]) # 索引
"{0:#08b}".format(5) # 宽度,补位
"{:06.2f}".format(1.234) # 保留两位小数
"{:,}".format(1234567) # 千分位
"[{:^10}]".format("abc") # 居中对齐
池化
字符串相同可以共享实例,因为它们内容相同,又不可变,所以共享没有问题,比较时候不需要额外计算,只要比指针,效率高
import sys
"__name__" is sys.intern("__name__")
a = "hello, world!"
b = "hello, world!"
a is b # 不同实例
sys.intern(a) is sys.intern("hello, world!") # 相同实例
字节数组¶
当我们谈论字节序列时候,更关心的存储和传输方式,面向类型时,更关心抽象属性
print(b"abc")
print(bytes("汉字", "utf-8"))
a = b"abc"
b = a + b"def"
b.startswith(b"abc")
b.upper()
bytes 是一次性内存分配,bytearray 可以按需扩张,更适合作为可读写缓冲区使用。如果有必要还可以为其提前分配足够的内存,避免中途扩张造成额外损耗
b = bytearray(b"ab")
len(b)
b.append(ord("c"))
b.extend(b"de")
b
b"abc" + b"123" # 加法操作
b"abc" * 2 # 乘法操作
内存视图可以直接引用字节数据的某个片段,支持的类型有 bytes, bytearray, array.array, NumPy 的某些类型等
a = bytearray([0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
v = memoryview(a)
x = v[2:5] # 视图片段,改变 x 也会改变原数据
x.hex()
a[3] = 0xee
x.hex()
x[1] = 0x13
a
a = b"\x10\x11"
v = memoryview(a)
v[1] = 0xee # bytes 是不可变类型
复制视图,可以用 tobytes, tolist 方法。复制后的数据与原对象无关,同样不会影响视图自身
a = bytearray([0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16])
v = memoryview(a)
x = v[2:5]
b = x.tobytes() # 复制视图
b
a[3] = 0xee
b
列表¶
列表内部由两部分组成,保存元素数量和内存分配计数的头部,以及存储指针的独立数组。所有元素项使用该数组保存指针引用,并不嵌入实际内容
list("abc")
list(range(3))
[x + 1 for x in range(6) if x % 2 == 0] # 列表推倒式
如果实现自定义列表,推荐基于 collections.UserList 包装类完成,除了统一 collections.abc 体系外,最重要的是该类型重载并完善了相关运算符算法
import collections
print(list.__bases__)
print(collections.UserList.__bases__)
class A(list): pass # 对比不同继承结果
type(A("abc") + list("de")) # 返回的是 list 不是 A
class B(collections.UserList): pass
type(B("abc") + list("de")) # 返回 B 类型
a = [1, 2]
b = a
a = a + [3, 4] # 不修改原对象
print(a)
print(b)
a = [1, 2]
b = a
a += [3, 4] # 修改原对象,编译器将 += 操作处理成 INPLACE_ADD 操作,修改原数据,而非新建对象
print(a)
print(b)
2 in [1, 2] # 判断元素是否存在习惯使用 in 方法
a = [0, 1, 2, 3, 4, 5]
del a[5] # 删除单个元素
a
del a[1:3] # 指定删除范围
a
a = [0, 2, 4, 6]
b = a[:2]
a[0] is b[0] # 复制引用,仍然指向同一个对象
a.insert(1, 1) # 对 a 列表操作,不会影响 b
a
b # 对列表自身的修改互不影响,对目标元素对象的修改是共享的
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"{self.name} {self.age}"
users = [User(f"user{i}", i) for i in (3, 1, 0, 2)]
users
users.sort(key = lambda u: u.age) # 可以指定排序条件
users
d = [3, 0, 2, 1]
sorted(d) # sorted 可以返回排序结果的复制品
import bisect
d = [0, 2, 4]
bisect.insort_left(d, 2) # bisect 可以向有序序列插入元素,使用二分查找可用来实现优先级队或一致性哈希算法
d
元组一般用来当做列表的只读版本使用
a = tuple([1, "abc"])
a[0] = 2 # 只读
a = (1, )
type(a)
b = (1)
type(b)
(1, 2) + (3, 4) # 支持与列表类似的操作,但是不能修改每次都返回新对象
a = (1, 2, 3)
b = a
a += (4, 5) # 创建新的 tuple
print(a)
print(b)
User = collections.namedtuple("User", "name,age")
issubclass(User, tuple)
u = User("kaka", 26)
u.name, u.age # 字段名访问
u[0] is u.name # 序列号访问
数组与列表元组本质区别在于元素单一类型和内容嵌入
import array
a = array.array("b", [0x11, 0x22, 0x33, 0x44])
memoryview(a).hex() # 用内存视图来看,内容嵌入而非指针
a.array.array("i")
a.append(100)
a.append(1.23) # 只能是单一类型嵌入
a = array.array("i", [1, 2, 3])
a.buffer_info() # 返回缓冲区内存地址和长度
a.extend(range(100000))
a.buffer_info()
字典¶
字典的值可以是任何类型,但是主键必须是哈希类型,列表,集合等不能作为主键使用,即使是元组这种不可变类型,其中的元素也不能引用可变类型元素
issubclass(list, collections.Hashable)
issubclass(int, collections.Hashable)
hash((1, 2, 3))
hash((1, 2, [3, 5]))
{"a": 1, "b":2} # 用大括号构建
dict(a = 1, b = 2) # 类型构造
kvs = (("a", 1), ["b", 2])
dict(kvs)
dict(zip("abc", range(3)))
dict(map(lambda k, v: (k, v+10), "abc", range(3))) # lambda 过滤数据
{k: v + 10 for k, v in zip("abc", range(3))} # 推导式处理数据
a = {"a": 1}
b = dict(a, b=2) # 在复制 a 基础上增加键值对
b
c = dict.fromkeys(b, 0) # 仅使用 b 主键,内容设为 0
c
d = dict.fromkeys(("counter1", "counter2"), 0) # 显式提供主键
d
x = dict(a=1)
x["b"] # 主键不存在,引发异常
"b" in x # 先判断
x.get("b", 100) # 主键不存在返回默认值 100
x.get("a", 100) # 主键存在,返回实际内容
x = {}
x.setdefault("a", 0) # 如果有 a,返回实际内容,否则新增 {a:0} 键值
x
x["a"] = 100
x.setdefault("a", 0)
{"b":2, "a":1} == {"a":1, "b":2} # 可以比较内容是否相同
Python 3 默认以视图关联字典内容,既能避免复制开销,又能同步观察字典变化
x = dict(a = 1, b = 2)
ks = x.keys() # 主键视图
"b" in ks
for k in ks: print(k, x[k]) # 用视图迭代字典
视图能同步读取字典内容,却无法修改,且可以选择不同粒度的内容进行传递,如此可将接收方限定为指定模式下的观察员
def test(d): # 传递键值视图 items,只能读取无法修改
for k, v in d:
print(k, v)
x = dict(a = 1)
d = x.items()
test(d)
a = dict(a = 1, b = 2)
b = dict(c = 3, b = 2)
ka = a.keys()
kb = b.keys()
ka & kb # 交集 -- 视图支持集合运算
ka | kb # 并集
ka - kb # 差集
ka ^ kb # 对称差集,仅在 a 或 b 中出现,也就是 交集 - 并集
a = dict(a = 1, b = 2)
b = dict(b = 20, c = 3)
ks = a.keys() & b.keys() # 可以限定条件,a 中必须存在的主键
a.update({k:b[k] for k in ks}) # 将交集结果提取待更新内容
a
默认字典可以当字典键不存在的时候给字典一个默认值
d = collections.defaultdict(lambda : 100)
d["a"]
d["b"] += 1
d
有序字典,可以明确记录主键抽次插入的次序
d = collections.OrderedDict()
d["z"] = 1
d["a"] = 2
d["x"] = 3
for k, v in d.items(): print(k, v) # 有序排列
计数器(Counter)对不存在的主键返回 0,而不会新增键值
d = collections.Counter()
d["a"] # 单纯访问不会新增键值
d["b"] += 1
d
链式字典(ChainMap)以单一借口访问多个字典内容,其自身并不存储数据,读操作会按参数顺序依次查找各字典,但修改操作(insert, update, delete)仅会针对第一个字典
a = dict(a = 1, b = 2)
b = dict(b = 20, c = 30)
x = collections.ChainMap(a, b)
x["b"], x["c"]
x["b"] = 999 # 更新,在第一字典执行
x["z"] = 888 # 新增,在第一字典执行
x
链式字典适合设计多层次上下文(context) 结构。合理上下文,要具备两个特征,首先是继承,所有设置可被调用链的后续函数读取。其次修改仅针对当前和后续逻辑,不应该向无关的父级传递
root = collections.ChainMap({"a":1})
child = root.new_child({"b": 200})
child["a"] = 100
child
child.parents
集合¶
集合存储非重复对象,所谓重复,是指除不是同一对象外,值也不能相等,判重公式: (a is b) OR (hash(a) == hash(b) AND a == b)
a = 1234
b = 1234
a is b # a ,b 内容相同,但不是同一对象
s = {a}
b in s # 使用内容相同的 b 判重
初始化和字典类似,使用大括号,但是初始化数据不是键值对,元素必须是可哈希类型
type({})
type({"a": 1})
type({1})
set((1, "a", 1.0))
frozenset(range(3)) # 不可变集合
{x + 1 for x in range(6) if x % 2 == 0}
s = {1}
f = frozenset(s) # 可以相互转换
f
set(f)
{1, 2} > {2, 1} # 支持大小,相等运算符
{1, 2} == {2, 1}
{1, 2} <= {1, 2, 3} # 子集
{1, 2, 3} >= {1, 2} # 超集
{1, 2} in {1, 2, 3} # 判断是否包含 {1, 2} 这一个元素(把这个集合看成一个元素)
和字典视图一样,集合也有交集,并集,差集,对称差集
{1, 2, 3} & {2, 3, 4} # 交集
{1, 2, 3} | {2, 3, 4} # 并集
{1, 2, 3} - {2, 3, 4} # 差集
{1, 2, 3} ^ {2, 3, 4} # 对称差集
x = {1, 2}
x |= {2, 3} # update
x
x = {1, 2}
x &= {2, 3} # intersection_update
x
x = {2, 1}
x.remove(2)
x.remove(2) # 删除操作可能引发异常
x.discard(2) # discard 不会引发异常
自定义类型虽然是可哈希类型,但默认实现并不足以完成集合去重操作
class User:
def __init__(self, uid, name):
self.uid = uid
self.name = name
issubclass(User, collections.Hashable)
u1 = User(1, "user1")
u2 = User(1, "user1")
s = set()
s.add(u1)
s.add(u2)
s
这里的原因是默认实现的 __hash__ 方法返回随机值,而 __eq__ 仅比较自身。所以需要重载这两个方法
class User:
def __init__(self, uid, name):
self.uid = uid
self.name = name
def __hash__(self):
return hash(self.uid)
def __eq__(self, other):
return self.uid == other.uid
u1 = User(1, "user1")
u2 = User(1, "user1")
s = set()
s.add(u1)
s.add(u2)
s
u1 in s
u2 in s # 仅检查 uid 字段